/*
  Realtime fur with OpenGL
  programmer Markus Ilmola
             markus.ilmola@pp.inet.fi
             http://markus_ilmola.tripod.com

  This program loads a cat head model (from the sims unleashed)
  and renders it with real-looking fur using the "fins and shells"-algorithm.
  This code is NOT optimized.

  Link with opengl32.lib and glu32.lib when compiling.
*/

/*-Include the necessary libraries.--------------------------------------------------------------*/
#include <windows.h>          // Windows
#include <gl\gl.h>            // OpenGL
#include <gl\glu.h>           // Glut
#include <gl\glext.h>         // Extensions
#include <stdio.h>            // For file handling
#include <time.h>             // For random numbers
#include <math.h>             // Sin, Cos etz...

/*-Include other stuff.--------------------------------------------------------------------------*/
#include "nehe.h"        // Nehes base code.
#include "vmath.h"       // Vector math.
#include "texture.h"     // For texture loading.
#include "model.h"       // For model loading

/*-Globals.--------------------------------------------------------------------------------------*/
float LightAmbient[]={ 1, 1, 1, 1 };
float LightDiffuse[]={ 1, 1, 1, 1};
float LightPosition[]={ 0.0f, 0.0f, 2.0f, 0 };

MODEL cat;
int cat_texture=0;
int fin_texture=0;
int shell_texture[16];

float fur_lenght=1;
float fur_scale=1;
float angle=0, angle2=0, angle3=0;


/*-This function generates the shell textures using noise.---------------------------------------*/
BOOL generate_shell_textures()
{
  /*-Locals.-------------------------------------------------------------------------------------*/
  long int i, ii;
  BYTE *data;
  int x, y;

  /*-Allocate memory.----------------------------------------------------------------------------*/
  glGenTextures(16, shell_texture);
  data=(BYTE *)malloc((4*(128*128))*sizeof(BYTE));
  if (!data) return FALSE;

  /*-Reset the data.-----------------------------------------------------------------------------*/
  for (i=0; i<128*128; i++)
  {
    data[4*i+0]=255;
    data[4*i+1]=255;
    data[4*i+2]=255;
    data[4*i+3]=0;
  }

  /*-Generate all 16 textures.-------------------------------------------------------------------*/
  for (i=15; i>=0; i--)
  {
    /*-Add some noise.---------------------------------------------------------------------------*/
	for (ii=0; ii<750; ii++)
    {
      x=rand()%128;
      y=rand()%128;
      data[4*(128*y+x)+3]=128+(128.0/i);  // Set alpha to opaque
    }
    /*-Make the texture.-------------------------------------------------------------------------*/
	glBindTexture (GL_TEXTURE_2D, shell_texture[i]);
    glPixelStorei (GL_UNPACK_ALIGNMENT, 1);
    glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
    glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexEnvf (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
    glTexImage2D(GL_TEXTURE_2D, 0, 4, 128, 128, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
  }

  /*-Clean up.-----------------------------------------------------------------------------------*/
  free(data);

  /*-Everything OK.------------------------------------------------------------------------------*/
  return TRUE;
}

/*-This function builds and renders the fins-----------------------------------------------------*/
void build_fins(MODEL *m, int mesh)
{
  /*-Locals.-------------------------------------------------------------------------------------*/
  int i;
  POLYGON *p;
  IJK v;
  float z;
  float fin_lenght=2;

  /*-Render the fins.----------------------------------------------------------------------------*/
  glBegin(GL_QUADS);
  for (i=0; i<m->mesh[mesh].number_of_polygons; i++)
  {
    p=&m->mesh[mesh].polygon[i];

	/*-Fin between vertex 0 and vertex 1.--------------------------------------------------------*/
    v.i=p->vertex[0]->x-p->vertex[1]->x;
    v.j=p->vertex[0]->y-p->vertex[1]->y;
    v.k=p->vertex[0]->z-p->vertex[1]->z;
    z=fur_scale*give_vector_lenght(&v);

    glNormal3f(p->vector[0]->i, p->vector[0]->j, p->vector[0]->k);

    glColor4f(0.25, 0.25, 0.25, 0.5);
    glMultiTexCoord2fARB(GL_TEXTURE0_ARB, 0, 1);
    glMultiTexCoord2fARB(GL_TEXTURE1_ARB, p->vertex[0]->u, p->vertex[0]->w);
    glVertex3f(p->vertex[0]->x, p->vertex[0]->y, p->vertex[0]->z);

    glColor4f(1, 1, 1, 0.5);
    glMultiTexCoord2fARB(GL_TEXTURE0_ARB, 0, 0);
    glMultiTexCoord2fARB(GL_TEXTURE1_ARB, p->vertex[0]->u, p->vertex[0]->w);
    glVertex3f(p->vertex[0]->x+fin_lenght*fur_lenght*p->vector[0]->i,
               p->vertex[0]->y+fin_lenght*fur_lenght*p->vector[0]->j,
               p->vertex[0]->z+fin_lenght*fur_lenght*p->vector[0]->k);

    glNormal3f(p->vector[1]->i, p->vector[1]->j, p->vector[1]->k);

    glColor4f(1, 1, 1, 0.5);
    glMultiTexCoord2fARB(GL_TEXTURE0_ARB, z, 0);
    glMultiTexCoord2fARB(GL_TEXTURE1_ARB, p->vertex[1]->u, p->vertex[1]->w);
    glVertex3f(p->vertex[1]->x+fin_lenght*fur_lenght*p->vector[1]->i,
               p->vertex[1]->y+fin_lenght*fur_lenght*p->vector[1]->j,
               p->vertex[1]->z+fin_lenght*fur_lenght*p->vector[1]->k);

    glColor4f(0.25, 0.25, 0.25, 0.5);
    glMultiTexCoord2fARB(GL_TEXTURE0_ARB, z, 1);
    glMultiTexCoord2fARB(GL_TEXTURE1_ARB, p->vertex[1]->u, p->vertex[1]->w);
    glVertex3f(p->vertex[1]->x, p->vertex[1]->y, p->vertex[1]->z);

    /*-Fin between vertex 1 and vertex 2.--------------------------------------------------------*/
	v.i=p->vertex[2]->x-p->vertex[1]->x;
    v.j=p->vertex[2]->y-p->vertex[1]->y;
    v.k=p->vertex[2]->z-p->vertex[1]->z;
    z=fur_scale*give_vector_lenght(&v);

    glNormal3f(p->vector[2]->i, p->vector[2]->j, p->vector[2]->k);

    glColor4f(0.25, 0.25, 0.25, 0.5);
    glMultiTexCoord2fARB(GL_TEXTURE0_ARB, 0, 1);
    glMultiTexCoord2fARB(GL_TEXTURE1_ARB, p->vertex[2]->u, p->vertex[2]->w);
    glVertex3f(p->vertex[2]->x, p->vertex[2]->y, p->vertex[2]->z);

    glColor4f(1, 1, 1, 0.5);
    glMultiTexCoord2fARB(GL_TEXTURE0_ARB, 0, 0);
    glMultiTexCoord2fARB(GL_TEXTURE1_ARB, p->vertex[2]->u, p->vertex[2]->w);
    glVertex3f(p->vertex[2]->x+fin_lenght*fur_lenght*p->vector[2]->i,
               p->vertex[2]->y+fin_lenght*fur_lenght*p->vector[2]->j,
               p->vertex[2]->z+fin_lenght*fur_lenght*p->vector[2]->k);

    glNormal3f(p->vector[1]->i, p->vector[1]->j, p->vector[1]->k);

    glColor4f(1, 1, 1, 0.5);
    glMultiTexCoord2fARB(GL_TEXTURE0_ARB, z, 0);
    glMultiTexCoord2fARB(GL_TEXTURE1_ARB, p->vertex[1]->u, p->vertex[1]->w);
    glVertex3f(p->vertex[1]->x+fin_lenght*fur_lenght*p->vector[1]->i,
               p->vertex[1]->y+fin_lenght*fur_lenght*p->vector[1]->j,
               p->vertex[1]->z+fin_lenght*fur_lenght*p->vector[1]->k);

    glColor4f(0.25, 0.25, 0.25, 0.5);
    glMultiTexCoord2fARB(GL_TEXTURE0_ARB, z, 1);
    glMultiTexCoord2fARB(GL_TEXTURE1_ARB, p->vertex[1]->u, p->vertex[1]->w);
    glVertex3f(p->vertex[1]->x, p->vertex[1]->y, p->vertex[1]->z);

    /*-Fin between vertex 2 and vertex 0.--------------------------------------------------------*/
    v.i=p->vertex[0]->x-p->vertex[2]->x;
    v.j=p->vertex[0]->y-p->vertex[2]->y;
    v.k=p->vertex[0]->z-p->vertex[2]->z;
    z=fur_scale*give_vector_lenght(&v);

    glNormal3f(p->vector[0]->i, p->vector[0]->j, p->vector[0]->k);

    glColor4f(0.25, 0.25, 0.25, 0.5);
    glMultiTexCoord2fARB(GL_TEXTURE0_ARB, 0, 1);
    glMultiTexCoord2fARB(GL_TEXTURE1_ARB, p->vertex[0]->u, p->vertex[0]->w);
    glVertex3f(p->vertex[0]->x, p->vertex[0]->y, p->vertex[0]->z);

    glColor4f(1, 1, 1, 0.5);
    glMultiTexCoord2fARB(GL_TEXTURE0_ARB, 0, 0);
    glMultiTexCoord2fARB(GL_TEXTURE1_ARB, p->vertex[0]->u, p->vertex[0]->w);
    glVertex3f(p->vertex[0]->x+fin_lenght*fur_lenght*p->vector[0]->i,
               p->vertex[0]->y+fin_lenght*fur_lenght*p->vector[0]->j,
               p->vertex[0]->z+fin_lenght*fur_lenght*p->vector[0]->k);

    glNormal3f(p->vector[2]->i, p->vector[2]->j, p->vector[2]->k);

    glColor4f(1, 1, 1, 0.5);
    glMultiTexCoord2fARB(GL_TEXTURE0_ARB, z, 0);
    glMultiTexCoord2fARB(GL_TEXTURE1_ARB, p->vertex[2]->u, p->vertex[2]->w);
    glVertex3f(p->vertex[2]->x+fin_lenght*fur_lenght*p->vector[2]->i,
               p->vertex[2]->y+fin_lenght*fur_lenght*p->vector[2]->j,
               p->vertex[2]->z+fin_lenght*fur_lenght*p->vector[2]->k);

    glColor4f(0.25, 0.25, 0.25, 0.5);
    glMultiTexCoord2fARB(GL_TEXTURE0_ARB, z, 1);
    glMultiTexCoord2fARB(GL_TEXTURE1_ARB, p->vertex[2]->u, p->vertex[2]->w);
    glVertex3f(p->vertex[2]->x, p->vertex[2]->y, p->vertex[2]->z);
  }
  glEnd();
}

/*-This function builds and renders the shells.--------------------------------------------------*/
void build_shells(MODEL *m, int mesh)
{
  /*-Locals.-------------------------------------------------------------------------------------*/
  int s, i;
  float z;
  POLYGON *p;
  IJK v1, v2;
  float l1, l2;

  /*-Render 16 shells.---------------------------------------------------------------------------*/
  for (s=0; s<16; s++)
  {
    /*-Set everything for this shell.------------------------------------------------------------*/
    z=(fur_lenght/15.0)+(float)s*(fur_lenght/15.0);
    glBindTexture(GL_TEXTURE_2D, shell_texture[s]);
    glColor4f(0.25+0.75*(s/15.0), 0.25+0.75*(s/15.0), 0.25+0.75*(s/15.0), 1);

	/*-Render the shell.-------------------------------------------------------------------------*/
    glBegin(GL_TRIANGLES);
    for (i=0; i<m->mesh[0].number_of_polygons; i++)
    {
      p=&cat.mesh[0].polygon[i];

	  glMultiTexCoord2fARB(GL_TEXTURE0_ARB, 0, 0);
      glMultiTexCoord2fARB(GL_TEXTURE1_ARB, p->vertex[0]->u, p->vertex[0]->w);
      glNormal3f(p->vector[0]->i, p->vector[0]->j, p->vector[0]->k);
      glVertex3f(p->vertex[0]->x+z*p->vector[0]->i,
                 p->vertex[0]->y+z*p->vector[0]->j,
                 p->vertex[0]->z+z*p->vector[0]->k);

      v1.i=p->vertex[1]->x-p->vertex[0]->x;
      v1.j=p->vertex[1]->y-p->vertex[0]->y;
      v1.k=p->vertex[1]->z-p->vertex[0]->z;
      l1=normalize_vector(&v1);
      glMultiTexCoord2fARB(GL_TEXTURE0_ARB, fur_scale*l1, 0);
      glMultiTexCoord2fARB(GL_TEXTURE1_ARB, p->vertex[1]->u, p->vertex[1]->w);
      glNormal3f(p->vector[1]->i, p->vector[1]->j, p->vector[1]->k);
      glVertex3f(p->vertex[1]->x+z*p->vector[1]->i,
                 p->vertex[1]->y+z*p->vector[1]->j,
                 p->vertex[1]->z+z*p->vector[1]->k);

      v2.i=p->vertex[2]->x-p->vertex[0]->x;
      v2.j=p->vertex[2]->y-p->vertex[0]->y;
      v2.k=p->vertex[2]->z-p->vertex[0]->z;
      l2=normalize_vector(&v2);
      glMultiTexCoord2fARB(GL_TEXTURE0_ARB, fur_scale*l2*dot_product(&v1, &v2),
                                            fur_scale*l2*sin(acos(dot_product(&v1, &v2))));
      glMultiTexCoord2fARB(GL_TEXTURE1_ARB, p->vertex[2]->u, p->vertex[2]->w);
      glNormal3f(p->vector[2]->i, p->vector[2]->j, p->vector[2]->k);
      glVertex3f(p->vertex[2]->x+z*p->vector[2]->i,
                 p->vertex[2]->y+z*p->vector[2]->j,
                 p->vertex[2]->z+z*p->vector[2]->k);
    }
    glEnd();
  }
}


/*-Initialize OpenGL.----------------------------------------------------------------------------*/
int initGL(GLvoid)
{
  /*-Init multitexturing.------------------------------------------------------------------------*/
  if (!InitMultitexture()) return FALSE;

  /*-Load models and textures.-------------------------------------------------------------------*/
  if (!generate_shell_textures()) return FALSE;
  if (!load_tga_texture("fin.tga", &fin_texture)) return FALSE;
  glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
  if (!load_tga_texture("cat.tga", &cat_texture)) return FALSE;
  if (!load_milkshape_model(&cat, "cat.m3d")) return FALSE;

  /*-Set OpenGL.---------------------------------------------------------------------------------*/
  glShadeModel(GL_SMOOTH);
  glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);

  glClearColor(1, 1, 1, 0);              // White background
  glClearDepth(1);

  glDepthFunc(GL_LEQUAL);
  glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);

  glLightfv(GL_LIGHT1, GL_AMBIENT, LightAmbient);
  glLightfv(GL_LIGHT1, GL_DIFFUSE, LightDiffuse);
  glLightfv(GL_LIGHT1, GL_POSITION,LightPosition);
  glColorMaterial(GL_FRONT_AND_BACK, GL_DIFFUSE);

  glEnable(GL_LIGHT1);
  glEnable(GL_LIGHTING);
  glEnable(GL_DEPTH_TEST);
  glEnable(GL_BLEND);
  glEnable(GL_TEXTURE_2D);
  glEnable(GL_DEPTH_TEST);
  glEnable(GL_COLOR_MATERIAL);

  /*-Set global variables.-----------------------------------------------------------------------*/
  fur_lenght=0.75;
  fur_scale=0.25;
  angle=0;
  angle2=0;
  angle3=0;
  srand(time(NULL));   // Seed for random number generator.

  /*-Everything OK.------------------------------------------------------------------------------*/
  return TRUE;
}

/*-This function renders everything.-------------------------------------------------------------*/
void render()
{
  /*-Clear the buffers.--------------------------------------------------------------------------*/
  glDepthMask(TRUE);   // Make sure the the writing to the depth-buffer is ON before clearing it!
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

  /*-Translate the model to the right place.-----------------------------------------------------*/
  glLoadIdentity();
  glTranslatef(0,0,-30);
  glRotatef(angle,1,0,0);
  glRotatef(angle2,0,1,0);
  glRotatef(angle3,0,0,1);

  /*-Render base (skin).-------------------------------------------------------------------------*/
  if (!keys[VK_B])
  {
    /*-Set texture units.------------------------------------------------------------------------*/
    glActiveTextureARB(GL_TEXTURE0_ARB);
    glEnable(GL_TEXTURE_2D);
    glBindTexture(GL_TEXTURE_2D, cat_texture);
    glActiveTextureARB(GL_TEXTURE1_ARB);
    glDisable(GL_TEXTURE_2D);
    /*-Set states.-------------------------------------------------------------------------------*/
    glEnable(GL_CULL_FACE);
    glDepthMask(TRUE);
    glEnable(GL_LIGHTING);
    /*-Render.-----------------------------------------------------------------------------------*/
    glColor4f(0.25, 0.25, 0.25, 1);  // The shadows of the fur makes the skin dark.
    glCallList(cat.id);              // Render the skin.
    glColor4f(1, 1, 1, 1);           // Eyes are light.
    glCallList(cat.id+1);            // Render the eyes.
    glColor4f(1, 1, 1, 0.75);
    glCallList(cat.id+2);
  }

  /*-Render fins.--------------------------------------------------------------------------------*/
  if (!keys[VK_F])
  {
    /*-Set texture units.------------------------------------------------------------------------*/
    glActiveTextureARB(GL_TEXTURE1_ARB);
    glBindTexture(GL_TEXTURE_2D, cat_texture);
	glEnable(GL_TEXTURE_2D);
	glActiveTextureARB(GL_TEXTURE0_ARB);
    glBindTexture(GL_TEXTURE_2D, fin_texture);
    glEnable(GL_TEXTURE_2D);
    /*-Set states.-------------------------------------------------------------------------------*/
	glDisable(GL_CULL_FACE);
    glDepthMask(FALSE);
	glDisable(GL_LIGHTING);
    /*-Render.-----------------------------------------------------------------------------------*/
    build_fins(&cat, 0);
  }

  /*-Render shells.------------------------------------------------------------------------------*/
  if (!keys[VK_S])
  {
    /*-Set texture units.------------------------------------------------------------------------*/
    glActiveTextureARB(GL_TEXTURE1_ARB);
    glBindTexture(GL_TEXTURE_2D, cat_texture);
	glEnable(GL_TEXTURE_2D);
	glActiveTextureARB(GL_TEXTURE0_ARB);
    glEnable(GL_TEXTURE_2D);
	/*-Set states.-------------------------------------------------------------------------------*/
    glEnable(GL_CULL_FACE);
    glEnable(GL_DEPTH_TEST);
    glDepthMask(TRUE);
    glEnable(GL_LIGHTING);
	/*-Render.-----------------------------------------------------------------------------------*/
	build_shells(&cat, 0);
  }

  /*-Swap the buffer to the screen.--------------------------------------------------------------*/
  SwapBuffers(hdc);
}

/*-The main function.----------------------------------------------------------------------------*/
int WINAPI WinMain(HINSTANCE hinstance, HINSTANCE hprevinstance, LPSTR lpcmdline, int ncmdshow)
{
  /*-Locals.-------------------------------------------------------------------------------------*/
  MSG msg;
  BOOL done=FALSE;
  DWORD start_time;
  DWORD frame_time;
  DWORD time;

  /*-Create the window.--------------------------------------------------------------------------*/
  if (!CreateGLWindow("Realtime fur with OpenGL", 800, 600, 32, FALSE)) return 0;

  /*-The main message loop.----------------------------------------------------------------------*/
  start_time=GetTickCount();
  while(!done)
  {
    if (PeekMessage(&msg,NULL,0,0,PM_REMOVE))
    {
      /*-Handle messages.------------------------------------------------------------------------*/
      if (msg.message==WM_QUIT)
      {
        done=TRUE;
      }
      else
      {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
      }
    }
    else
    {
      if (active)
      {
	  /*-Make the app run at the same speed on all computers.------------------------------------*/
	  time=GetTickCount();
      frame_time=time-start_time;
      start_time=time;
      if (frame_time<1) frame_time=1;      // Max 1000 fps
      if (frame_time>200) frame_time=200;  // Min 5 fps

      /*-Make the program do its magic.----------------------------------------------------------*/
      if (!keys[VK_P])
      {
        angle+=frame_time*0.025,
        angle2+=frame_time*0.025;
        angle3+=frame_time*0.025;
      }
      if (keys[VK_R])
      {
        angle=20;
        angle2=30;
        angle3=0;
      }
      if (keys[VK_ESCAPE]) done=TRUE;

	  render();
      }
    }
  }

  /*-Clean up.-----------------------------------------------------------------------------------*/
  free_model(&cat);
  KillGLWindow();
  return (msg.wParam);
}
